#include <iostream>
#include <fstream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <algorithm>
#include <memory>
#include <list>
#include <vector>

#ifdef HAVE_CONFIG_H
#include "../mind_config.h"
#endif
#include "OptionContainer.hpp"
#include "FOptionContainer.hpp"
#include "Logger.hpp"


// GLOBALS
extern OptionContainer o;
extern Logger log;

// INPLEMENTATION

// clear out the list

void IPList::reset() {
    iplist.clear();
    iprangelist.clear();
    ipsubnetlist.clear();
    hostlist.clear();
}

// search for IP in list of individual IPs, ranges, subnets and - if reverse lookups are enabled - hostnames.

bool IPList::inList(const std::string &ipstr, std::string *&host) const {
    struct in_addr addr;
    inet_aton(ipstr.c_str(), &addr);
    uint32_t ip = ntohl(addr.s_addr);
    // start with individual IPs
    if (std::binary_search(iplist.begin(), iplist.end(), ip)) {
        // only return a hostname if that's what we matched against
        delete host;
        host = NULL;
        return true;
    }

    // ranges
    for (std::list<ipl_rangestruct>::const_iterator i = iprangelist.begin(); i != iprangelist.end(); ++i) {
        if ((ip >= i->startaddr) && (ip <= i->endaddr)) {
            delete host;
            host = NULL;
            return true;
        }
    }

    // subnets
    for (std::list<ipl_subnetstruct>::const_iterator i = ipsubnetlist.begin(); i != ipsubnetlist.end(); ++i) {
        if (i->maskedaddr == (ip & i->mask)) {
            delete host;
            host = NULL;
            return true;
        }
    }

    // hostnames
    // TODO - take in a suggested hostname, look up only if not supplied, and return suggestion if found
    if (o.reverse_client_ip_lookups) {
        std::auto_ptr<std::deque<String> > hostnames;
        if (host == NULL)
            hostnames.reset(ipToHostname(ipstr.c_str()));
        else {
            hostnames.reset(new std::deque<String>);
            hostnames->push_back(*host);
        }
        for (std::deque<String>::iterator i = hostnames->begin(); i != hostnames->end(); ++i) {
            if (std::binary_search(hostlist.begin(), hostlist.end(), *i)) {
                delete host;
                host = new std::string(i->toCharArray());
                return true;
            }
        }
        // Even if we don't match anything, return a hostname
        // if desired for logging and we don't already have one.
        if (o.log_client_hostnames && (host == NULL) && (hostnames->size() > 0))
            host = new std::string(hostnames->front().toCharArray());
    }

    return false;
}

// read in a list linking IPs, subnets & IP ranges to filter groups

bool IPList::readIPMelangeList(const char *filename) {
    // load in the list file
    std::ifstream input(filename);
    if (!input) {
        log.writeToLog(1, "Error reading file (does it exist?): %s", filename);
        return false;
    }

    // compile regexps for determining whether a list entry is an IP, a subnet (IP + mask), or a range
    RegExp matchIP, matchSubnet, matchRange;
#ifdef HAVE_PCRE
    matchIP.comp("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$");
    matchSubnet.comp("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$");
    matchRange.comp("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}-\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$");
#else
    matchIP.comp("^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$");
    matchSubnet.comp("^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$");
    matchRange.comp("^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}-[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$");
#endif

    // read in the file
    String line;
    char buffer[ 2048 ];
    while (input) {
        if (!input.getline(buffer, sizeof ( buffer))) {
            break;
        }
        // ignore comments
        if (buffer[0] == '#')
            continue;
        // ignore blank lines
        if (strlen(buffer) < 7)
            continue;
        line = buffer;
#ifdef MIND_DEBUG
        std::cout << "line: " << line << std::endl;
#endif
        // store the IP address (numerically, not as a string) and filter group in either the IP list, subnet list or range list
        if (matchIP.match(line.toCharArray())) {
            struct in_addr address;
            if (inet_aton(line.toCharArray(), &address)) {
                uint32_t addr = ntohl(address.s_addr);
                iplist.push_back(addr);
            }
        } else if (matchSubnet.match(line.toCharArray())) {
            struct in_addr address;
            struct in_addr addressmask;
            String subnet(line.before("/"));
            String mask(line.after("/"));
            if (inet_aton(subnet.toCharArray(), &address) && inet_aton(mask.toCharArray(), &addressmask)) {
                ipl_subnetstruct s;
                uint32_t addr = ntohl(address.s_addr);
                s.mask = ntohl(addressmask.s_addr);
                // pre-mask the address for quick comparison
                s.maskedaddr = addr & s.mask;
                ipsubnetlist.push_back(s);
            }
        } else if (matchRange.match(line.toCharArray())) {
            struct in_addr addressstart;
            struct in_addr addressend;
            String start(line.before("-"));
            String end(line.after("-"));
            if (inet_aton(start.toCharArray(), &addressstart) && inet_aton(end.toCharArray(), &addressend)) {
                ipl_rangestruct r;
                r.startaddr = ntohl(addressstart.s_addr);
                r.endaddr = ntohl(addressend.s_addr);
                iprangelist.push_back(r);
            }
        }            // hmmm. the line didn't match any of our regular expressions.
            // assume it's a hostname.
        else {
            hostlist.push_back(line);
        }
    }
    input.close();
#ifdef MIND_DEBUG
    std::cout << "starting sort" << std::endl;
#endif
    std::sort(iplist.begin(), iplist.end());
    std::sort(hostlist.begin(), hostlist.end());
#ifdef MIND_DEBUG
    std::cout << "sort complete" << std::endl;
    std::cout << "ip list dump:" << std::endl;
    std::vector<uint32_t>::iterator i = iplist.begin();
    while (i != iplist.end()) {
        std::cout << "IP: " << *i << std::endl;
        ++i;
    }
    std::cout << "subnet list dump:" << std::endl;
    std::list<ipl_subnetstruct>::iterator j = ipsubnetlist.begin();
    while (j != ipsubnetlist.end()) {
        std::cout << "Masked IP: " << j->maskedaddr << " Mask: " << j->mask << std::endl;
        ++j;
    }
    std::cout << "range list dump:" << std::endl;
    std::list<ipl_rangestruct>::iterator k = iprangelist.begin();
    while (k != iprangelist.end()) {
        std::cout << "Start IP: " << k->startaddr << " End IP: " << k->endaddr << std::endl;
        ++k;
    }
    std::cout << "host list dump:" << std::endl;
    std::vector<String>::iterator l = hostlist.begin();
    while (l != hostlist.end()) {
        std::cout << "Hostname: " << *l << std::endl;
        ++l;
    }
#endif
    return true;
}
